/**
 * Copyright 2013 Bhushan
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package org.s4j.signals.helper;

import java.lang.reflect.Method;

import org.s4j.signals.annotation.Signal;
import org.s4j.signals.annotation.SignalDefinitions;
import org.s4j.signals.annotation.Slot;
import org.s4j.signals.core.Bridgeable;
import org.s4j.signals.core.SignalContainer;
import org.s4j.signals.core.SlotContainer;
import org.s4j.signals.exception.NoSuchSignalException;
import org.s4j.signals.exception.NoSuchSlotException;

/**
 * Utility methods to help in fetching signals and slots defined in the objects.
 * 
 * @author Bhushan
 * 
 */
public class ReflectionHelper {

    /**
     * Fetches a signal from the given object. Signals need to be defined as part of the class definitions within the
     * SignalDefinitions annotation.
     * 
     * @param object
     *            object that contains the defined signals
     * @param signalName
     *            name of the signal that needs to be fetched
     * @return a container having the necessary data about the signal and the object
     * @throws NoSuchSignalException
     *             in case the signal was not defined
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static SignalContainer fetchSignal(final Bridgeable object, final String signalName)
            throws NoSuchSignalException {
        assert object != null;
        assert signalName != null;
        assert signalName.isEmpty() != true;

        // TODO: Need to check if this works with inheritance
        Class clss = object.getClass();
        SignalDefinitions definitions = (SignalDefinitions) clss.getAnnotation(SignalDefinitions.class);

        if (definitions == null) {
            throw new NoSuchSignalException("No signal definitions found: Type: " + object.getClass() + " SignalName: "
                    + signalName);
        }

        Signal[] definedSignals = definitions.signals();

        for (Signal signal : definedSignals) {
            if (signal.name().equals(signalName)) {
                return new SignalContainer(object, signalName, signal.paramTypes());
            }
        }

        throw new NoSuchSignalException("Unable to find signal with name: " + signalName);
    }

    /**
     * Fetches a slot from the object. Slots are defined as part of the methods. The method searches for the given slot
     * and pulls out the relevant details.
     * 
     * @param object
     *            object that has the slot defined
     * @param slotName
     *            name of the slot as per the annotation
     * @return a container having the necessary data about the object and the annotation.
     * @throws NoSuchSlotException
     *             in case the slot was not found in the class
     */
    @SuppressWarnings("rawtypes")
    public static SlotContainer fetchSlot(final Bridgeable object, final String slotName) throws NoSuchSlotException {
        assert object != null;
        assert slotName != null;
        assert slotName.isEmpty() != true;

        // TODO: Need to check if this works with inheritance
        Class clss = object.getClass();
        for (Method method : clss.getMethods()) {
            Slot annotation = method.getAnnotation(Slot.class);

            if (annotation != null && slotName.equals(annotation.value())) {
                return new SlotContainer(object, slotName, method);
            }
        }

        throw new NoSuchSlotException("Unable to find slot with name: " + slotName);
    }
}
